/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opensymphony.able.apt;

import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Filer.Location;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.util.SimpleDeclarationVisitor;

import javax.persistence.Entity;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

/**
 * @version $Revision: 11 $
 */
public class EntityPropertiesFileClassVisitor extends SimpleDeclarationVisitor {
    private final AnnotationProcessorEnvironment env;

    private Properties entities = new Properties();
    private Map<String, Collection<String>> packageToEntityMap = new HashMap();

    public EntityPropertiesFileClassVisitor(
            final AnnotationProcessorEnvironment env) {
        this.env = env;
    }

    public void visitClassDeclaration(ClassDeclaration declaration) {
        if (matchesDeclaration(declaration)) {
            String simpleName = declaration.getSimpleName();
            entities.setProperty(declaration.getQualifiedName(), simpleName);
            String packageName = declaration.getPackage().getQualifiedName();
            Collection<String> collection = packageToEntityMap.get(packageName);
            if (collection == null) {
                collection = new ArrayList<String>();
                packageToEntityMap.put(packageName, collection);
            }
            collection.add(simpleName);
        }
    }

    protected boolean matchesDeclaration(ClassDeclaration declaration) {
        Entity annotation = declaration.getAnnotation(Entity.class);
        return annotation != null;
    }

    public void writeFile(AbleAnnotationProcessor processor) {
        writeEntityPropertiesFile(processor);
        writeEntityPropertiesFileIncludingOldContents(processor);
        writeJaxbFile(processor);
    }

    public void writeEntityPropertiesFileIncludingOldContents(AbleAnnotationProcessor processor) {
        OutputStream out = null;
        try {
            out = env.getFiler().createBinaryFile(Location.CLASS_TREE, "",
                    new File("META-INF/services/able/entities.properties"));
        }
        catch (Exception e) {
            throw new RuntimeException("Failed to create file: " + e, e);
        }
        Properties oldProperties = new Properties();
        try {
            File file = new File("target/classes/META-INF/services/able/entities.properties");
            if (file.exists()) {
                oldProperties.load(new FileInputStream(file));
            }
        }
        catch (FileNotFoundException e1) {
            // ignore
        }
        catch (IOException e) {
            System.out.println("Failed to load old entities.properties file: " + e);
        }
        try {
            oldProperties.putAll(entities);
            oldProperties.store(out, "All JPA Entity Beans auto-generated by Able");
        }
        catch (IOException e) {
            throw new RuntimeException(
                    "Failed to write entity properties file: " + e, e);
        }
        finally {
            if (out != null) {
                try {
                    out.close();
                }
                catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    public void writeEntityPropertiesFile(AbleAnnotationProcessor processor) {
        OutputStream out = null;
        try {
            out = env.getFiler().createBinaryFile(Location.CLASS_TREE, "",
                    new File("META-INF/services/able/changedEntities.properties"));
        }
        catch (Exception e) {
            throw new RuntimeException("Failed to create file: " + e, e);
        }
        try {
            entities.store(out, "Changed JPA Entity Beans auto-generated by Able");
        }
        catch (IOException e) {
            throw new RuntimeException(
                    "Failed to write entity properties file: " + e, e);
        }
        finally {
            if (out != null) {
                try {
                    out.close();
                }
                catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    public void writeJaxbFile(AbleAnnotationProcessor processor) {
        Set<Entry<String, Collection<String>>> entrySet = packageToEntityMap
                .entrySet();
        for (Entry<String, Collection<String>> entry : entrySet) {
            writeJaxbFile(processor, entry.getKey(), entry.getValue());
        }

    }

    public void writeJaxbFile(AbleAnnotationProcessor processor,
                              String packageName, Collection<String> names) {
        PrintWriter out = null;
        try {
            String name = packageName + "/jaxb.index";
            System.out.println("Creating source file: " + name);
            out = env.getFiler().createTextFile(Location.CLASS_TREE, "",
                    new File(packageName.replace('.', '/') + "/jaxb.index"),
                    "UTF8");
        }
        catch (Exception e) {
            throw new RuntimeException("Failed to create file: " + e, e);
        }
        try {
            for (String name : names) {
                out.println(name);
            }
        }
        finally {
            if (out != null) {
                out.close();
            }
        }
    }
}
